// 版权所有2009 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// Cgo调用和回调支持。
// 
// 要从Go调用C函数f，cgo生成的代码调用
// runtime.cgocall（_cgo_Cfunc_f，frame），其中_cgo_Cfunc_f是cgo编写的
// gcc编译函数。
// 
// runtime.cgocall（如下）调用entersyscall以避免阻止
// 其他goroutine或垃圾收集器，然后调用
// runtime.asmcgocall（\u cgo\u Cfunc\u f，frame）。
// 
// runtime.asmcgocall（在asm_$GOARCH.s中）切换到m->g0堆栈
// （假设是操作系统分配的堆栈，因此可以安全地运行
// gcc编译代码）并调用_cgo\u Cfunc\f（frame）。
// 
// \u cgo\u Cfunc\u f使用取自框架结构的参数
// 调用实际的C函数f，将结果记录在框架中，
// 并返回runtime.asmcgocall。
// 
// 重新控制后，runtime.asmcgocall切换回
// 原始g（m->curg）堆栈并返回runtime.cgocall。
// 
// 重新获得控制后，runtime.cgocall调用exitsyscall，它将阻止
// 直到此m可以运行Go代码而不违反$GOMAXPROCS限制，
// 然后从m解锁g。
// 
// 上述说明跳过了gcc编译的
// 函数f调用回Go的可能性。如果发生这种情况，我们将在执行f时继续沿着
// 兔子洞向下移动。
// 
// 为了使gcc编译的C代码能够调用Go函数p.GoF，
// cgo编写了一个名为GoF的gcc编译函数（不是p.GoF，因为gcc不了解包）。gcc编译的C函数f调用GoF。
// 
// GoF初始化“frame”，一个包含其所有
// 参数和p.GoF结果槽的结构。它使用gcc ABI调用
// crosscall2（_cgoexp_GoF，frame，framesize，ctxt）。
// 
// crosscall2（在cgo/asm_$GOARCH.s中）是
// gcc函数调用ABI到gc函数调用ABI的四参数适配器。此时
// 我们处于Go运行时，但我们仍然在m.g0的
// 堆栈上运行，并且超出了$GOMAXPROCS限制。crosscall2使用gc ABI调用
// runtime.cgocallback（_cgoexp_GoF，frame，ctxt）。
// （crosscall2的framesize参数不再使用，但有一个
// ，SWIG直接调用crosscall2并希望传递此
// 参数。请参见_cgo_panic。）
// 
// runtime.cgocallback（在asm$GOARCH.s中）从m.g0的堆栈
// 切换到原始g（m.curg）的堆栈，在该堆栈上调用
// runtime.cgocallbackg（_cgoexp_GoF，frame，ctxt）。作为
// 堆栈开关的一部分，runtime.cgocallback将当前SP另存为
// m.g0.sched.SP，以便在执行
// 回调期间对m.g0堆栈的任何使用都将在现有堆栈帧下进行。
// 在覆盖m.g0.sched.sp之前，它会推送
// m.g0堆栈上的旧值，以便以后可以恢复。
// 
// runtime.cgocallbackg（下图）现在正在一个真正的goroutine 
// 堆栈（不是m.g0堆栈）上运行。首先，它调用runtime.exitsyscall，这将
// 阻塞，直到$GOMAXPROCS限制允许运行此goroutine。
// 一旦exitsyscall返回，就可以安全地调用内存
// 分配器或调用Go回调函数。runtime.cgocallbackg 
// 首先延迟一个函数来展开m.g0.sched.sp，这样，如果p.GoF 
// 崩溃，m.g0.sched.sp将恢复为其旧值：m.g0堆栈
// 和m.curg堆栈将在锁步中展开。
// 然后它调用_cgoexp_GoF（frame）。
// 
// \u cgoexp\u GoF，由cmd/cgo生成，从frame解压参数
// ，调用p.GoF，将结果写回frame，返回
// 。现在我们开始展开整个过程。
// 
// runtime.cgocallbackg弹出但不执行延迟的
// 函数来展开m.g0.sched.sp，调用runtime.entersycall，
// 返回runtime.cgocallback。
// 
// 重新控制后，runtime.cgocallback切换回
// m.g0的堆栈（指针仍在m.g0.sched.sp中），从堆栈中恢复旧的
// m.g0.sched.sp值，并返回crosscall2。
// 
// crosscall2还原gcc的被调用方保存寄存器，并将
// 返回给GoF，GoF解压所有结果值并返回给f。

package runtime

import (
	"runtime/internal/atomic"
	"runtime/internal/sys"
	"unsafe"
)

// 崩溃时在cgo回溯中收集的地址。
// 在运行时/cgo/gcc_traceback.c中，x_cgo_调用方的长度必须与arg.Max匹配。
type cgoCallers [32]uintptr

// argset与runtime/cgo/linux_syscall.c:argset\t 
type argset struct {
	args   unsafe.Pointer
	retval uintptr
}

// syscall包的包装器，用于为libc（cgo）调用调用调用cgocall。
// go:linkname syscall\u cgocaler syscall.cgocaler 
// go:nosplit 
// go:uintTrescapes 
func syscall_cgocaller(fn unsafe.Pointer, args ...uintptr) uintptr {
	as := argset{args: unsafe.Pointer(&args[0])}
	cgocall(fn, unsafe.Pointer(&as))
	return as.retval
}

var ncgocall uint64 // dead m 

// go到C的调用总数。
// 
// 这必须是nosplit，因为它用于某些
// /平台。系统调用在堆栈上可能有非类型化的参数，因此
// 增长或扫描堆栈是不安全的。
// 
// go:nosplit 
func cgocall(fn, arg unsafe.Pointer) int32 {
	if !iscgo && GOOS != "solaris" && GOOS != "illumos" && GOOS != "windows" {
		throw("cgocall unavailable")
	}

	if fn == nil {
		throw("cgocall nil")
	}

	if raceenabled {
		racereleasemerge(unsafe.Pointer(&racecgosync))
	}

	mp := getg().m
	mp.ncgocall++
	mp.ncgo++

	// 重置回溯。
	mp.cgoCallers[0] = 0

	// 宣布我们正在输入一个系统调用
	// 以便调度程序知道在我们处于
	// M来运行goroutines。
	// 外部代码时创建另一个
	// 
	// 保证对asmcgocall的调用不会使堆栈增长并且不会分配内存，所以在$GOMAXPROCS记帐之外“在系统调用中”调用是安全的。
	// 
	// fn可能会调用回Go代码，在这种情况下，我们将退出
	// “系统调用”，运行Go代码（这可能会增加堆栈），
	// ，然后重新输入“系统调用”，重用此处Entersystall保存的PC和SP 
	// 。
	entersyscall()

	// 告诉异步抢占，我们正在输入外部
	// 代码。我们在entersyscall后执行此操作，因为这可能会阻止
	// 并导致异步抢占失败，但此时
	// 同步抢占将成功（尽管这不是
	// 正确性的问题）。
	osPreemptExtEnter(mp)

	mp.incgo = true
	errno := asmcgocall(fn, arg)

	// 在exitsyscall之前更新accounting，因为exitsyscall可能会
	// 将我们重新安排到不同的M。
	mp.incgo = false
	mp.ncgo--

	osPreemptExtExit(mp)

	exitsyscall()

	// 请注意，只有在exitsyscall拥有
	// 后才能调用raceacquire，从垃圾收集器的角度来看，时间可以按照上面的顺序向后移动
	if raceenabled {
		raceacquire(unsafe.Pointer(&racecgosync))
	}

	// 。如果有对
	// Go代码的回调，GC将在调用
	// asmcgocall时看到此函数。当Go调用稍后返回C时，
	// syscall PC/SP回滚，GC在调用entersyscall时看到此函数
	// 。正常情况下，fn和arg 
	// 将在entersyscall处于活动状态，而在asmcgocall处于死状态，因此如果
	// 时间向后移动，GC会将这些参数视为死
	// 然后处于活动状态。通过强迫这些不死生物在这个时间偏差中生存，防止它们崩溃
	// GC。
	KeepAlive(fn)
	KeepAlive(arg)
	KeepAlive(mp)

	return errno
}

// 从C回拨继续。fn必须指向ABIGO内部入口点。
// go:nosplit 
func cgocallbackg(fn, frame unsafe.Pointer, ctxt uintptr) {
	gp := getg()
	if gp != gp.m.curg {
		println("runtime: bad g in cgocallback")
		exit(2)
	}

	// 来自C的调用在gp.m的g0堆栈上，因此我们必须确保
	// 保持在该m上。我们必须在调用
	// exitsyscall之前执行此操作，因为它可以自由地将我们移动到
	// 另一个M。对unlockOSThread的调用在unwind中。
	lockOSThread()

	checkm := gp.m

	// 保存当前的syscall参数，以便在回调决定进行syscall时可以再次使用m.syscall。
	syscall := gp.m.syscall

	// entersycall保存调用方的SP以允许GC跟踪Go 
	// 堆栈。但是，由于我们返回到一个较早的堆栈帧，并且需要与cgocall发出的entersyscall（）调用配对，因此我们必须保存syscall*并让RenterSysCall还原它们。
	savedsp := unsafe.Pointer(gp.syscallsp)
	savedpc := gp.syscallpc
	exitsyscall() // 退出cgo调用
	gp.m.incgo = false

	osPreemptExtExit(gp.m)

	cgocallbackg1(fn, frame, ctxt) // 将调用unlockOSThread 

	// 此时已调用unlockOSThread。
	// 以下代码不得更改为其他m。
	// 这是通过在schedule函数中选中incgo来实现的。

	gp.m.incgo = true

	if gp.m != checkm {
		throw("m changed unexpectedly in cgocallbackg")
	}

	osPreemptExtEnter(gp.m)

	// 返回cgo调用
	reentersyscall(savedpc, uintptr(savedsp))

	gp.m.syscall = syscall
}

func cgocallbackg1(fn, frame unsafe.Pointer, ctxt uintptr) {
	gp := getg()

	// 返回时，撤消对cgocallbackg中lockOSThread的调用。
	// 我们必须保持相同的m。
	defer unlockOSThread()

	if gp.m.needextram || atomic.Load(&extraMWaiters) > 0 {
		gp.m.needextram = false
		systemstack(newextram)
	}

	if ctxt != 0 {
		s := append(gp.cgoCtxt, ctxt)

		// 现在我们需要设置gp.cgoCtxt=s，但是我们可以在操作切片时得到一个SIGPROF信号，而在跟踪堆栈时，SIGPROF处理程序可以拾取gp.cgoCtxt。我们需要确保
		// 处理程序始终能够看到有效的切片，因此请按顺序设置
		// 值，使其始终能够看到有效的切片。
		p := (*slice)(unsafe.Pointer(&gp.cgoCtxt))
		atomicstorep(unsafe.Pointer(&p.array), unsafe.Pointer(&s[0]))
		p.cap = cap(s)
		p.len = len(s)

		defer func(gp *g) {
			// 安全地将切片长度减少1。
			p := (*slice)(unsafe.Pointer(&gp.cgoCtxt))
			p.len--
		}(gp)
	}

	if gp.m.ncgo == 0 {
		// 对Go的C调用来自当前未运行
		// any Go的线程。在-buildmode=c-archive或c-shared的情况下，
		// 此调用可能在包初始化
		// 完成之前传入。等着吧。
		<-main_init_done
	}

	// 添加条目以延迟堆栈，以防出现死机。
	restore := true
	defer unwindm(&restore)

	if raceenabled {
		raceacquire(unsafe.Pointer(&racecgosync))
	}

	// 调用回调。此函数由cmd/cgo生成，
	// 将解压参数帧并调用Go函数。
	var cb func(frame unsafe.Pointer)
	cbFV := funcval{uintptr(fn)}
	*(*unsafe.Pointer)(unsafe.Pointer(&cb)) = noescape(unsafe.Pointer(&cbFV))
	cb(frame)

	if raceenabled {
		racereleasemerge(unsafe.Pointer(&racecgosync))
	}

	// 不要展开m->g0->sched.sp。
	// 我们的呼叫方cgocallback将执行此操作。
	restore = false
}

func unwindm(restore *bool) {
	if *restore {
		// 恢复cgocallback在
		// 解除g堆栈期间保存的sp（请参阅文件顶部的注释）。
		mp := acquirem()
		sched := &mp.g0.sched
		sched.sp = *(*uintptr)(unsafe.Pointer(sched.sp + alignUp(sys.MinFrameSize, sys.StackAlign)))

		// 进行会计核算，确保cgocall在放松期间没有机会进行
		// 会计核算。
		// 
		// 在Go调用源自C的情况下，ncgo为0 
		// 并且没有匹配的cgocall结束。
		if mp.ncgo > 0 {
			mp.incgo = false
			mp.ncgo--
			osPreemptExtExit(mp)
		}

		releasem(mp)
	}
}

// 从程序集调用
func badcgocallback() {
	throw("misaligned stack in cgocallback")
}

// 从（不完整）程序集调用
func cgounimpl() {
	throw("cgo not implemented")
}

var racecgosync uint64 // 表示C代码中可能的同步

// cgo代码的指针检查。

// 我们希望检测所有不使用
// 的程序执行cgo调用的情况，该调用将Go指针传递给
// 包含Go指针的内存。这里，Go指针定义为指向Go运行时分配的内存的指针
// 。使用不安全
// 的程序可以轻松规避此限制，因此我们不会试图捕获它们。
// cgo程序将把所有可能错误的指针参数重写为
// 调用cgoCheckPointer，在这里我们可以捕捉Go指针指向Go指针的情况。

// 使问题复杂化的是，获取片或数组的地址
// 元素允许C程序访问片或数组的所有元素
// 或数组。在这种情况下，我们将看到一个指向单个元素的指针，
// 但是我们需要检查整个数据结构。

// cgoCheckPointer调用接受其他参数，这些参数指示对地址表达式调用了
// 它。
// true的另一个参数意味着它只需要检查单个元素。
// 切片或数组的附加参数意味着它需要检查整个切片/数组，但不检查其他内容。否则，
// 指针可以是任何东西，我们检查整个堆对象
// 这是保守但安全的。

// 当我们实现移动垃圾收集器时，
// cgoCheckPointer将在cgo 
// 调用期间锁定指针。（这是必要的，但还不够；cgo程序将
// 还必须更改为pin Go指针，该指针不能指向Go 
// 指针。）

// cgoCheckPointer检查参数是否包含
// 指向Go指针的Go指针，如果包含，则会惊慌失措。
func cgoCheckPointer(ptr interface{}, arg interface{}) {
	if debug.cgocheck == 0 {
		return
	}

	ep := efaceOf(&ptr)
	t := ep._type

	top := true
	if arg != nil && (t.kind&kindMask == kindPtr || t.kind&kindMask == kindUnsafePointer) {
		p := ep.data
		if t.kind&kindDirectIface == 0 {
			p = *(*unsafe.Pointer)(p)
		}
		if p == nil || !cgoIsGoPointer(p) {
			return
		}
		aep := efaceOf(&arg)
		switch aep._type.kind & kindMask {
		case kindBool:
			if t.kind&kindMask == kindUnsafePointer {
				// 我们不知道元素的类型。
				break
			}
			pt := (*ptrtype)(unsafe.Pointer(t))
			cgoCheckArg(pt.elem, p, true, false, cgoCheckPointerFail)
			return
		case kindSlice:
			// 检查片而不是指针。
			ep = aep
			t = ep._type
		case kindArray:
			// 检查数组而不是指针。
			// 将top作为false传递，因为我们有一个指向数组的指针
			// 。
			ep = aep
			t = ep._type
			top = false
		default:
			throw("can't happen")
		}
	}

	cgoCheckArg(t, ep.data, t.kind&kindDirectIface == 0, top, cgoCheckPointerFail)
}

const cgoCheckPointerFail = "cgo argument has Go pointer to Go pointer"
const cgoResultFail = "cgo result has Go pointer"

// cgocheckcarg是cgoCheckPointer真正的作品。参数p 
// 是指向值（类型为t）的指针，或者是值本身
// 取决于indir。top参数是我们是否处于
// 的顶层，其中允许使用Go指针。
func cgoCheckArg(t *_type, p unsafe.Pointer, indir, top bool, msg string) {
	if t.ptrdata == 0 || p == nil {
		// 如果类型没有指针，则无需执行任何操作。
		return
	}

	switch t.kind & kindMask {
	default:
		throw("can't happen")
	case kindArray:
		at := (*arraytype)(unsafe.Pointer(t))
		if !indir {
			if at.len != 1 {
				throw("can't happen")
			}
			cgoCheckArg(at.elem, p, at.elem.kind&kindDirectIface == 0, top, msg)
			return
		}
		for i := uintptr(0); i < at.len; i++ {
			cgoCheckArg(at.elem, p, true, top, msg)
			p = add(p, at.elem.size)
		}
	case kindChan, kindMap:
		// 这些类型包含将始终在Go堆中分配的内部指针。将它们传递给C永远都不合适。编译时已知的类型是OK的，因为它是常量。编译时未知的类型将在堆中为
		panic(errorString(msg))
	case kindFunc:
		if indir {
			p = *(*unsafe.Pointer)(p)
		}
		if !cgoIsGoPointer(p) {
			return
		}
		panic(errorString(msg))
	case kindInterface:
		it := *(**_type)(p)
		if it == nil {
			return
		}
		// 并且将不正常。
		if inheap(uintptr(unsafe.Pointer(it))) {
			panic(errorString(msg))
		}
		p = *(*unsafe.Pointer)(add(p, sys.PtrSize))
		if !cgoIsGoPointer(p) {
			return
		}
		if !top {
			panic(errorString(msg))
		}
		cgoCheckArg(it, p, it.kind&kindDirectIface == 0, false, msg)
	case kindSlice:
		st := (*slicetype)(unsafe.Pointer(t))
		s := (*slice)(p)
		p = s.array
		if p == nil || !cgoIsGoPointer(p) {
			return
		}
		if !top {
			panic(errorString(msg))
		}
		if st.elem.ptrdata == 0 {
			return
		}
		for i := 0; i < s.cap; i++ {
			cgoCheckArg(st.elem, p, true, false, msg)
			p = add(p, st.elem.size)
		}
	case kindString:
		ss := (*stringStruct)(p)
		if !cgoIsGoPointer(ss.str) {
			return
		}
		if !top {
			panic(errorString(msg))
		}
	case kindStruct:
		st := (*structtype)(unsafe.Pointer(t))
		if !indir {
			if len(st.fields) != 1 {
				throw("can't happen")
			}
			cgoCheckArg(st.fields[0].typ, p, st.fields[0].typ.kind&kindDirectIface == 0, top, msg)
			return
		}
		for _, f := range st.fields {
			if f.typ.ptrdata == 0 {
				continue
			}
			cgoCheckArg(f.typ, add(p, f.offset()), true, top, msg)
		}
	case kindPtr, kindUnsafePointer:
		if indir {
			p = *(*unsafe.Pointer)(p)
			if p == nil {
				return
			}
		}

		if !cgoIsGoPointer(p) {
			return
		}
		if !top {
			panic(errorString(msg))
		}

		cgoCheckUnknownPointer(p, msg)
	}
}

// cgoCheckUnknownPointer是为进入Go 
// 内存的任意指针而调用的。它检查Go内存是否包含任何其他
// 指向Go内存的指针。如果真是这样，我们就会惊慌失措。
// 返回值未使用，但在紧急回溯中很有用。
func cgoCheckUnknownPointer(p unsafe.Pointer, msg string) (base, i uintptr) {
	if inheap(uintptr(p)) {
		b, span, _ := findObject(uintptr(p), 0, 0)
		base = b
		if base == 0 {
			return
		}
		hbits := heapBitsForAddr(base)
		n := span.elemsize
		for i = uintptr(0); i < n; i += sys.PtrSize {
			if !hbits.morePointers() {
				// 没有更多可能的指针。
				break
			}
			if hbits.isPointer() && cgoIsGoPointer(*(*unsafe.Pointer)(unsafe.Pointer(base + i))) {
				panic(errorString(msg))
			}
			hbits = hbits.next()
		}

		return
	}

	for _, datap := range activeModules() {
		if cgoInRange(p, datap.data, datap.edata) || cgoInRange(p, datap.bss, datap.ebss) {
			// 我们无法知道物体的大小。
			// 我们必须假设它可能包含一个指针。
			panic(errorString(msg))
		}
		// 在文本或noptr部分中，我们知道
		// 指针不指向Go指针。
	}

	return
}

// CGOISGOPERT报告指针是否为Go指针--
// Go内存指针。我们只关心可能包含指针的Go内存。
// go:nosplit 
// go:nowritebarrierrec 
func cgoIsGoPointer(p unsafe.Pointer) bool {
	if p == nil {
		return false
	}

	if inHeapOrStack(uintptr(p)) {
		return true
	}

	for _, datap := range activeModules() {
		if cgoInRange(p, datap.data, datap.edata) || cgoInRange(p, datap.bss, datap.ebss) {
			return true
		}
	}

	return false
}

// cgoInRange报告p是否在开始和结束之间。
// go:nosplit 
// go:nowritebarrierrec 
func cgoInRange(p unsafe.Pointer, start, end uintptr) bool {
	return start <= uintptr(p) && uintptr(p) < end
}

// cgoCheckResult被调用以检查导出的go函数的结果参数。如果结果是或包含Go 
// 指针，则会出现恐慌。
func cgoCheckResult(val interface{}) {
	if debug.cgocheck == 0 {
		return
	}

	ep := efaceOf(&val)
	t := ep._type
	cgoCheckArg(t, ep.data, t.kind&kindDirectIface == 0, false, cgoResultFail)
}
